package calculator.model;


import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

import java.io.IOException;
import java.io.FileNotFoundException;

import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.PrintWriter;


// This is a model class for a Product domain.
public class ProductModel {
    
    private String productsFilePath;
    private List<Product> products;    

    public ProductModel(String productsFilePath){
        this.productsFilePath = productsFilePath;
        products = new ArrayList<Product>();
        generateProducts();
    }
    

    // Retrieve products from a txt file.
    // Store them in an ArrayList.
    private void generateProducts() {
        FileReader fr = null;
        BufferedReader br = null;

        try {            
            fr = new FileReader(productsFilePath);
            br = new BufferedReader(fr);
            String line;
            while ( ((line= br.readLine()) != null) && (!line.isEmpty()) ) {
                String[] elements = line.split(":");
                
                String name = elements[0];
                String measure = elements[1];
                int calories = Integer.parseInt(elements[2].trim());
                double protein = Double.parseDouble(elements[3].trim());
                double fat = Double.parseDouble(elements[4].trim());
                double carbohydrate = Double.parseDouble(elements[5].trim());
                
                Product nextProduct;
                if ( elements.length == 6) {           
                    nextProduct = new Product(name, measure, calories, protein, fat, carbohydrate);                    
                } else {
                    double alcohol = Double.parseDouble(elements[6].trim());            
                    nextProduct = new AlcoholicProduct(name, measure, calories, protein, fat, carbohydrate, alcohol);
                }
                products.add(nextProduct);
            }

        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        } finally {
            try {
                fr.close();
            } catch (IOException ioe) {
                 System.out.println(ioe.getMessage());
            }
        }
    }

    
    // Return a list of all products.
    public List<Product> getAllProducts() {
        return Collections.unmodifiableList(products); 
    }
    
    
    // Return a product with a given name. If it doesn't exist throws an exception.
    public Product getProduct(String name) throws FormException {
        
        List<String> errors = validateName (name);
        
        if( !errors.isEmpty() ) {
            throw new FormException(errors);
        }
        
        Product existingProduct = checkForProduct(name);      
        return existingProduct; 
    }
    

    // Add a new product to the products lists so that it stays in alphabetical order.
    // Also save it in the products file.
    public Product addProduct(String name, String measure, String caloriesStr, String proteinStr,
            String fatStr, String carbohydrateStr, String alcoholStr) throws FormException {
        
        List<String> errors = new ArrayList<String>();        
        
        // Verify that the name attribute is entered properly.
        // This is done differently than in all the other cases. 
        if(name.length() == 0) {            
            errors.add("Nazwa produktu musi byc podana.");
        } else if (name.contains(":")) {
            errors.add("Nazwa produktu nie może zawierać znaku dwukropka.");
        } else if (name.length() > 18) {
            errors.add("Nazwa produktu nie może przekraczać 18 znaków.");
        } else {
            Product existingProduct = checkForProduct(name);            
            if(existingProduct != null) {
                errors.add("Produkt o wybranej nazwie już istnieje.");
            }
        }       
        
        errors = validateRest (errors, measure, caloriesStr, proteinStr, fatStr, carbohydrateStr, alcoholStr);
                
        if( !errors.isEmpty() ) {
            throw new FormException(errors);
        }
        
        int calories = Integer.parseInt(caloriesStr);
        double protein = Double.parseDouble(proteinStr);
        double fat = Double.parseDouble(fatStr);
        double carbohydrate = Double.parseDouble(carbohydrateStr);
        double alcohol = Double.parseDouble(alcoholStr);
                
        Product newProduct = null;
        if ( alcohol == 0 ) {
            newProduct = new Product(name, measure, calories, protein, fat, carbohydrate);
        } else {
            newProduct = new AlcoholicProduct(name, measure, calories, protein, fat, carbohydrate, alcohol);
        }
        
        int oldListSize = products.size();
        
        // If the name of the product we are adding lexicographically precedes  
        // any of the names of products in the list add it to the list in a proper place.
        for (Product nextProduct : products) {
            if( name.compareToIgnoreCase(nextProduct.getName()) < 0  ) {                
                products.add( products.indexOf(nextProduct), newProduct );
                break;
            }
        }
            
        // If the name of the product we are adding lexicographically follows  
        // all names of products already in the list add the new product at the end of the list.
        if(oldListSize == products.size()) {
            products.add(newProduct);
        }
            
        // Save the new product in the products file.
        saveProductsList();
        
        return newProduct;
    }
    
    
    // Remove a product from the products list.
    // Also save changes in the products file.
    public Product removeProduct(String name) throws FormException {
        
        List<String> errors = validateName (name);
        
        if( !errors.isEmpty() ) {
            throw new FormException(errors);
        }
        
        Product existingProduct = checkForProduct(name);
        products.remove(existingProduct);
        saveProductsList();
                
        return existingProduct;        
    }
    
    
    // Modify a product in the products list.
    // Also save changes in the products file.
    public Product modifyProduct(String name, String measure, String caloriesStr, String proteinStr,
            String fatStr, String carbohydrateStr, String alcoholStr) throws FormException {
                        
        List<String> errors = validateName (name);
        errors = validateRest (errors, measure, caloriesStr, proteinStr, fatStr, carbohydrateStr, alcoholStr);
        
        if( !errors.isEmpty() ) {
            throw new FormException(errors);
        }
        
        Product existingProduct = checkForProduct(name); 
        
        if ( !(existingProduct instanceof AlcoholicProduct) && (Double.parseDouble(alcoholStr) != 0) ) {
            errors.add(name + " nie jest produktem alkoholowym.\nPole 'Zawartość alkoholu' musi być równe 0.");
            throw new FormException(errors);
        } else if ( (existingProduct instanceof AlcoholicProduct) && (Double.parseDouble(alcoholStr) == 0) ) {
            errors.add(name + " jest produktem alkoholowym.\nPole 'Zawartość alkoholu' nie może być równe 0.");
            throw new FormException(errors);
        }
        
        existingProduct.setMeasure(measure);
        existingProduct.setCalories( Integer.parseInt(caloriesStr) );
        existingProduct.setProtein(Double.parseDouble(proteinStr));
        existingProduct.setFat(Double.parseDouble(fatStr));
        existingProduct.setCarbohydrate(Double.parseDouble(carbohydrateStr));
        if ( existingProduct instanceof AlcoholicProduct) {
            AlcoholicProduct alcoholicProduct = (AlcoholicProduct)existingProduct;
            alcoholicProduct.setAlcohol(Double.parseDouble(alcoholStr));
        }
        saveProductsList();            
        
        return existingProduct;
    }
    
    
    // Look for a product with a given name. 
    private Product checkForProduct(String name) {
        Product product = null;
        
        for (Product nextProduct : products) {
            if( nextProduct.getName().equalsIgnoreCase(name) ) {
                product = nextProduct;
                break;
            }
        }
        
        return product;
    }
    

    // Save products in the products file.
    private void saveProductsList() {

        FileWriter fw = null;
        PrintWriter pw = null;

        try {
            // Delete existing products in the file.
            fw = new FileWriter(productsFilePath, false);
            pw = new PrintWriter(fw);
            pw.println();

            // Append products from the new list.
            fw = new FileWriter(productsFilePath, true);
            pw = new PrintWriter(fw);
            
            for (Product nextProduct : products) {
                
                String name = nextProduct.getName();
                String measure = nextProduct.getMeasure();
                int calories = nextProduct.getCalories();
                double protein = nextProduct.getProtein();
                double fat = nextProduct.getFat();
                double carbohydrate = nextProduct.getCarbohydrate();
                String nextLine = name + ":" + measure + ":" + calories + ":" + protein + ":" + fat + ":" + carbohydrate;
                
                if ( nextProduct instanceof AlcoholicProduct ) {
                    AlcoholicProduct alcoholicProduct = (AlcoholicProduct)nextProduct;
                    nextLine = nextLine + ":" + alcoholicProduct.getAlcohol();
                }
                
                pw.print(nextLine);
                pw.println();
            }  
            
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        } finally {
            try {
                fw.close();
            } catch (IOException ioe) {
                System.out.println(ioe.getMessage());
            }
        }
    }
    
    
    // Verify that the name attribute is entered properly.
    private List<String> validateName (String name) {
        
        List<String> errors = new ArrayList<String>();
                
        if(name.length() == 0) {            
            errors.add("Nazwa produktu musi byc podana.");
        } else {
            Product existingProduct = checkForProduct(name);            
            if(existingProduct == null) {
                errors.add("Produkt o wybranej nazwie nie istnieje.");
            }
        }
        
        return errors;
    }
    
    
    // Verify that all the other attributes are entered properly.
    private List<String> validateRest (List<String> errors, String measure, String caloriesStr, String proteinStr,
            String fatStr, String carbohydrateStr,  String alcoholStr) {
        
        int calories = 0;
        try {
            calories = Integer.parseInt(caloriesStr);
        } catch (NumberFormatException nfe) {
            errors.add("Pole 'Kalorie' musi być liczbą całkowitą.");
        }
        if( calories != 0 && (calories<0 || calories>999 ) ) {            
            errors.add("Pole 'Kalorie' musi być liczbą od 0 do 999.");           
        }
        
        double protein = 0.0;
        try {
            protein = Double.parseDouble(proteinStr);
        } catch (NumberFormatException nfe) {
            errors.add("Pole 'Białko' musi być liczbą rzeczywistą.");
        }
        if( protein != 0 && (protein<0 || protein>999 ) ) {            
            errors.add("Pole 'Białko' musi być liczbą od 0 do 999.");           
        }
        
        double fat = 0.0;
        try {
            fat = Double.parseDouble(fatStr);
        } catch (NumberFormatException nfe) {
            errors.add("Pole 'Tłuszcz' musi być liczbą rzeczywistą.");
        }
        if( fat != 0 && (fat<0 || fat>999 ) ) {            
            errors.add("Pole 'Tłuszcz' musi być liczbą od 0 do 999.");           
        }
        
        double carbohydrate = 0.0;
        try {
            carbohydrate = Double.parseDouble(carbohydrateStr);
        } catch (NumberFormatException nfe) {
            errors.add("Pole 'Węglowodany' musi być liczbą rzeczywistą.");
        }
        if( carbohydrate != 0 && (carbohydrate<0 || carbohydrate>999 ) ) {            
            errors.add("Pole 'Węglowodany' musi być liczbą od 0 do 999.");           
        }
        
        double alcohol = 0.0;
        try {
            alcohol = Double.parseDouble(alcoholStr);
        } catch (NumberFormatException nfe) {
            errors.add("Pole 'Alkohol' musi być liczbą rzeczywistą.");
        }
        if( alcohol != 0 && (alcohol<0 || alcohol>100 ) ) {            
            errors.add("Pole 'Alkohol' musi być liczbą od 0 do 100.");           
        }
        
        if(measure.length() == 0) {            
            errors.add("Jednostka miary musi byc podana.");
        } else if (measure.contains(":")) {
            errors.add("Jednostka miary nie może zawierać znaku dwukropka.");
        } else if (measure.length() > 8) {
            errors.add("Jednostka miary nie może przekraczać 8 znaków.");
        } else if (alcohol!=0) {
            if ( !measure.endsWith("ml") ) {
                errors.add("Jednostka miary alkoholi musi być podana w ml \n(np. 100ml).");
            } else {
                String ml = measure.substring(0, measure.length()-2);
                try {
                    Integer.parseInt(ml.trim());
                } catch (NumberFormatException nfe) {
                    errors.add("Jednostka miary alkoholi musi być podana w ml \n(np. 100ml).");
                }
            }
        }
        
        return errors;
    }


}
